package org.jugile.util;

/*

Copyright (C) 2007-2011 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * ""
 * 
 * @author jukka.rahkonen@iki.fi
 */
public class SaxParser extends Jugile implements ISaxListener {

	private Node root = null;
	private Node node = null;
	private boolean htmlmode = false;
	
	public SaxParser(boolean htmlmode) {
		this.htmlmode = htmlmode;
	}
	public SaxParser() {
		htmlmode = false; // default mode is xml
	}
	
	public void startTag(String tag, int linenum) {
		tag = tag.trim();
		if (tag.startsWith("?")) return;
		boolean empty = false;
		if (tag.endsWith("/")) empty = true;
		if (tag.contains(" ")) {
			// parse attributes
			int idx = tag.indexOf(" ");
			String name = tag.substring(0, idx);
			createNode(name, linenum);
			String rest = tag.substring(idx).trim();
		    Pattern p = Pattern.compile("([-:\\w]+)=\"([^\"]*)\"");
		    Matcher m = p.matcher(rest);
		    while (m.find()) node.attr(m.group(1), m.group(2));
		} else {
			createNode(tag, linenum);
		}
		if (empty) node = node.parent();
	}

	
	private Node createNode(String tag, int linenum) {
		if (root == null) {
			root = new Node(tag);
			node = root;
		} else {
			node = node.add(tag);
		}
		node.setLinenum(linenum);
		return node;
	}

	public void content(String cont, boolean cdata) {
		cont = cont.trim();
		if (empty(cont)) return;
		if (node == null) return;
		if (htmlmode) {
			for (String line : cont.split("\n")) node.add("",line.trim()); // html
		} else {
			if (cdata) {
				node.addCdata(cont);
				cdata = false;
			} else {
				node.value(cont); // xml
			}
		}
	}


	public void endTag(String tag) {
		if (node == null) return;
		node = node.parent();
	}

	public Node getRoot() { return root; }
	
	public static Node parseXml(String filename) {
		SaxParser p = new SaxParser(false);
		parse(filename, p);
		return p.getRoot();
	}
	public static Node parseHtml(String filename) {
		SaxParser p = new SaxParser(true);
		parse(filename, p);
		return p.getRoot();		
	}
	public static Node parseXmlString(String data) {
		PullReader pr = new PullReader(data);
		SaxParser p = new SaxParser(false);
		parse(pr,p,"UTF8");
		return p.getRoot();
	}
	
	// -------- parser ---------
	
	private enum State {
		START, TAG, CONTENT, ENDTAG, COMMENT, CDATA
	};

	public static void parse(String filename, ISaxListener listener) {
		parse(filename,listener,"UTF8");
	}
	public static void parse(String filename, ISaxListener listener, String enc) {
		try {
			InputStreamReader isr = new InputStreamReader(getResource(filename), enc);
			PullReader pr = new PullReader(isr);
			parse(pr,listener,enc);
		} catch (Exception e) { fail(e); }
	}
	public static void parse(InputStream is, ISaxListener listener, String enc) {
		try {
			InputStreamReader isr = new InputStreamReader(is, enc);		
			PullReader pr = new PullReader(isr);
			parse(pr,listener,enc);
		} catch (Exception e) { fail(e); }
		
	}
	public static void parse(PullReader pr, ISaxListener listener, String enc) {
		try {
			int linenum = 1;
			boolean cdata = false;
			State st = State.CONTENT;
			StringBuffer buf = new StringBuffer();
			while (pr.hasNext()) {
				char ch = pr.next(); 
				if (ch == '\n') linenum++;
				if (st == State.CONTENT) {
					if (ch == '<') {
						listener.content(buf.toString(), cdata);
						cdata = false;
						st = State.START;
						continue;
					}
					buf.append(ch);
					continue;
				}

				if (st == State.START) {
					buf = new StringBuffer();
					if (ch == '/') {
						st = State.ENDTAG;
						continue;
					}
					if (ch == '!') {
						if (pr.match("--")) {
							st = State.COMMENT;
						} else if (pr.match("[CDATA[")) {
							st = State.CDATA;
							buf = new StringBuffer();
							cdata = true;
						} else {
							// xml first line tag?
							st = State.CONTENT;
						}
						continue;
					}
					st = State.TAG;
					buf.append((char)ch);
					continue;
				}

				if (st == State.CDATA) {
					if (ch == ']') {
						if (pr.match("]>")) {
							st = State.CONTENT;
						}
					} else {
						buf.append(ch);
					}
				}
				
				if (st == State.COMMENT) {
					if (ch == '-') {
						if (pr.match("->")) {
							st = State.CONTENT;
						}
					}
					continue;
				}
				if (st == State.TAG) {
					if (ch == '>') {
						listener.startTag(buf.toString(), linenum);
						st = State.CONTENT;
						buf = new StringBuffer();
						continue;
					}
					buf.append(ch);
					continue;
				}

				if (st == State.ENDTAG) {
					if (ch == '>') {
						listener.endTag(buf.toString());
						st = State.CONTENT;
						buf = new StringBuffer();
						continue;
					}
					buf.append((char)ch);
					continue;
				}

			}
			pr.close();
			listener.content(buf.toString(),false);
		} catch (Exception e) {fail(e);}
	}

	
}
